package cn.lg.soar.core.manager;

import cn.lg.soar.common.util.hardware.SystemUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;

/**
 * 权限管理器
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 * @param <T> 角色数据类型（通常是String）
 */
public class AuthenticationManager<T> {

    protected static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationManager.class);

    private final Function<Long, Collection<T>> roleSupplier;
    private final Function<T, Collection<String>> permitSupplier;
    private final Cache<Long, Set<T>> userRoleCache;
    private final Cache<T, IPermitValidator> rolePermitCache;
    private final Set<T> EMPTY_ROLES = Collections.emptySet();

    /**
     * @param roleSupplier 角色数据供应器
     * @param permitSupplier 权限数据供应器
     */
    public AuthenticationManager(
            Function<Long, Collection<T>> roleSupplier,
            Function<T, Collection<String>> permitSupplier
    ) {
        this(roleSupplier, permitSupplier, Duration.ofMinutes(5), SystemUtils.TOTAL >> 16);
    }

    /**
     * @param roleSupplier 角色数据供应器
     * @param permitSupplier 权限数据供应器
     * @param userRoleCache 角色数据缓存
     * @param rolePermitCache 权限数据缓存
     */
    public AuthenticationManager(
            Function<Long, Collection<T>> roleSupplier,
            Function<T, Collection<String>> permitSupplier,
            Cache<Long, Set<T>> userRoleCache,
            Cache<T, IPermitValidator> rolePermitCache
    ) {
        this.roleSupplier = roleSupplier;
        this.userRoleCache = userRoleCache;
        this.permitSupplier = permitSupplier;
        this.rolePermitCache = rolePermitCache;
    }

    /**
     * @param roleSupplier 角色数据供应器
     * @param permitSupplier 权限数据供应器
     * @param cacheExpire 缓存有效期
     * @param cacheSize 缓存最大值
     */
    public AuthenticationManager(
            Function<Long, Collection<T>> roleSupplier,
            Function<T, Collection<String>> permitSupplier,
            Duration cacheExpire,
            long cacheSize
    ) {
        this.roleSupplier = roleSupplier;
        this.permitSupplier = permitSupplier;

        userRoleCache = Caffeine.newBuilder()
                .maximumSize(cacheSize)
                .initialCapacity(128)
                .expireAfterWrite(cacheExpire)
                .build();
        LOGGER.info("user-role cache max size {}", cacheSize);

        long rpSize = cacheSize >> 6;
        if (rpSize < 16) {
            rpSize = 16;
        }
        rolePermitCache = Caffeine.newBuilder()
                .maximumSize(rpSize)
                .initialCapacity(16)
                .expireAfterWrite(cacheExpire)
                .build();
        LOGGER.info("role-permit cache max size {}", rpSize);
    }

    /**
     * 验证用户权限
     * @param userId 用户id
     * @param permit 权限码
     * @return
     */
    public boolean hasPermit(Long userId, String permit) {
        Set<T> roles = getRoles(userId);
        for (T role : roles) {
            // 验证角色权限
            if (getPermitValidator(role).has(permit)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户角色
     * @param userId 用户id
     * @param role 角色
     * @return
     */
    public boolean hasRole(Long userId, T role) {
        Set<T> roles = getRoles(userId);
        return roles.contains(role);
    }

    /**
     * 根据用户id获取角色
     * @param userId
     * @return
     */
    private Set<T> getRoles(Long userId) {
        return userRoleCache.get(userId, k -> {
            Collection<T> roles = roleSupplier.apply(userId);
            if (roles == null) {
                return EMPTY_ROLES;
            }
            return new HashSet<>(roles);
        });
    }

    /**
     * 根据角色id获取权限
     * @param role
     * @return
     */
    private IPermitValidator getPermitValidator(T role) {
        return rolePermitCache.get(role, k -> {
            Collection<String> permits = permitSupplier.apply(role);
            if (permits == null) {
                return IPermitValidator.EMPTY;
            }
            // 构造权限验证器
            return IPermitValidator.build(permits);
        });
    }

}
